using Core;
using SuperSocket.Channel;
using SuperSocket.ProtoBase;
using SuperSocket.Server;

namespace Server;

public sealed class PlaySession : AppSession
{
    private CancellationTokenSource _connectionToken = null!;
    private readonly IPackageEncoder<PlayPacket> _encoder;
    private readonly PacketDispatcher _packetDispatcher = new();
    private readonly PacketIdentifierProvider _packetIdentifierProvider = new();

    public PlaySession(IPackageEncoder<PlayPacket> encoder)
    {
        _encoder = encoder;
    }

    public ulong UserId { get; set; }

    protected override ValueTask OnSessionConnectedAsync()
    {
        _connectionToken = new CancellationTokenSource();

        return base.OnSessionConnectedAsync();
    }

    protected override ValueTask OnSessionClosedAsync(CloseEventArgs e)
    {
        _connectionToken.Cancel();
        _connectionToken.Dispose();

        return base.OnSessionClosedAsync(e);
    }

    internal ValueTask DispatchAsync(PlayPacketWithIdentifier packet)
    {
        ThrowIfSessionClosed();

        _packetDispatcher.TryDispatch(packet);

        return ValueTask.CompletedTask;
    }

    internal ValueTask SendPacketAsync(PlayPacket packet)
    {
        ThrowIfSessionClosed();

        return Channel.SendAsync(_encoder, packet);
    }

    internal async ValueTask<TRespPacket> SendAndReceivePacketAsync<TRespPacket>(
        PlayPacketWithIdentifier packet,
        CancellationToken cancellationToken)
        where TRespPacket : PlayPacketWithIdentifier
    {
        ThrowIfSessionClosed();

        using var tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _connectionToken.Token);

        var token = tokenSource.Token;
        var identifier = packet.Identifier = _packetIdentifierProvider.GetNextPacketIdentifier();

        using var packetAwaitable = _packetDispatcher.AddAwaitable<TRespPacket>(identifier);

        try
        {
            await SendPacketAsync(packet);
        }
        catch (Exception e)
        {
            packetAwaitable.Fail(e);
            Logger.LogError(e, $"{UserId} 发送封包抛出一个异常 key：{packet.Key}");
        }

        try
        {
            return await packetAwaitable.WaitAsync(token);
        }
        catch (Exception e)
        {
            if (e is TimeoutException)
                Logger.LogError(e, $"{UserId} 等待封包调度超时 key：{packet.Key}");

            throw;
        }
    }

    private void ThrowIfSessionClosed()
    {
        if (!Channel.IsClosed)
            return;

        if (!_connectionToken.IsCancellationRequested)
            _connectionToken.Cancel();

        throw new SessionClosedException();
    }
}

public sealed class SessionClosedException : Exception
{
    public SessionClosedException()
    {
    }

    public SessionClosedException(string? message)
        : base(message)
    {
    }
}
